// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包pem实现pem数据编码，该编码源于隐私
// 增强型邮件。目前，PEM编码最常用于TLS密钥和
// 证书。见RFC 1421。
package pem

import (
	"bytes"
	"encoding/base64"
	"errors"
	"io"
	"sort"
	"strings"
)

// 块表示PEM编码的结构。
// None
// 编码形式为：
// -----开始类型-----
// 标题
// base64编码字节
// -----端型-----
// 其中，标题可能是键：值行的空序列。
type Block struct {
	Type    string            // 类型，取自序言（即“RSA私钥”）。
	Headers map[string]string // 可选标题。
	Bytes   []byte            // 内容的解码字节。通常是DER编码的ASN.1结构。
}

// getLine从给定字节生成第一条\r\n或\n划定的行
// 大堆该行不包括尾随空格或尾随新字符
// 行字节。字节数组的其余部分（也不包括新行
// 字节），并且该值将始终小于原始值
// 论点
func getLine(data []byte) (line, rest []byte) {
	i := bytes.IndexByte(data, '\n')
	var j int
	if i < 0 {
		i = len(data)
		j = i
	} else {
		j = i + 1
		if i > 0 && data[i-1] == '\r' {
			i--
		}
	}
	return bytes.TrimRight(data[0:i], " \t"), data[j:]
}

// removeSpacesAndTabs返回其输入的一个副本，其中包含所有空格和选项卡
// 已删除，如果有。否则，输入将原封不动地返回。
// None
// base64解码器已经跳过了换行符，所以我们不需要这样做
// 在这里过滤它们。
func removeSpacesAndTabs(data []byte) []byte {
	if !bytes.ContainsAny(data, " \t") {
		// 快速路径；PEM中的大多数base64数据都包含换行符，但
		// 没有空格和制表符。跳过额外的分配，开始工作。
		return data
	}
	result := make([]byte, len(data))
	n := 0

	for _, b := range data {
		if b == ' ' || b == '\t' {
			continue
		}
		result[n] = b
		n++
	}

	return result[0:n]
}

var pemStart = []byte("\n-----BEGIN ")
var pemEnd = []byte("\n-----END ")
var pemEndOfLine = []byte("-----")

// 解码将找到下一个PEM格式的块（证书、私钥）
// 等）输入。它返回该块和输入的其余部分。如果
// 找不到PEM数据，p为零，整个输入以
// 休息
func Decode(data []byte) (p *Block, rest []byte) {
	// pemStart以一个新行开始。但是,在一开始,
	// 字节数组，我们将接受没有它的开始字符串。
	rest = data
	if bytes.HasPrefix(data, pemStart[1:]) {
		rest = rest[len(pemStart)-1 : len(data)]
	} else if i := bytes.Index(data, pemStart); i >= 0 {
		rest = rest[i+len(pemStart) : len(data)]
	} else {
		return nil, data
	}

	typeLine, rest := getLine(rest)
	if !bytes.HasSuffix(typeLine, pemEndOfLine) {
		return decodeError(data, rest)
	}
	typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]

	p = &Block{
		Headers: make(map[string]string),
		Type:    string(typeLine),
	}

	for {
		// 此循环终止，因为getLine的第二个结果是
		// 总是比它的论点小。
		if len(rest) == 0 {
			return nil, data
		}
		line, next := getLine(rest)

		i := bytes.IndexByte(line, ':')
		if i == -1 {
			break
		}

		// TODO（agl）：需要处理跨行的值。
		key, val := line[:i], line[i+1:]
		key = bytes.TrimSpace(key)
		val = bytes.TrimSpace(val)
		p.Headers[string(key)] = string(val)
		rest = next
	}

	var endIndex, endTrailerIndex int

	// 如果没有标题，则可能出现结束行
	// 立即，没有领先的新线。
	if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
		endIndex = 0
		endTrailerIndex = len(pemEnd) - 1
	} else {
		endIndex = bytes.Index(rest, pemEnd)
		endTrailerIndex = endIndex + len(pemEnd)
	}

	if endIndex < 0 {
		return decodeError(data, rest)
	}

	// 在结束行的“----”之后，应该有相同的类型
	// 然后是最后五个破折号。
	endTrailer := rest[endTrailerIndex:]
	endTrailerLen := len(typeLine) + len(pemEndOfLine)
	if len(endTrailer) < endTrailerLen {
		return decodeError(data, rest)
	}

	restOfEndLine := endTrailer[endTrailerLen:]
	endTrailer = endTrailer[:endTrailerLen]
	if !bytes.HasPrefix(endTrailer, typeLine) ||
		!bytes.HasSuffix(endTrailer, pemEndOfLine) {
		return decodeError(data, rest)
	}

	// 行必须仅以空格结尾。
	if s, _ := getLine(restOfEndLine); len(s) != 0 {
		return decodeError(data, rest)
	}

	base64Data := removeSpacesAndTabs(rest[:endIndex])
	p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
	n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
	if err != nil {
		return decodeError(data, rest)
	}
	p.Bytes = p.Bytes[:n]

	// -1是因为我们可能只匹配了pemEnd而没有
	// 如果PEM块为空，则引导换行符。
	_, rest = getLine(rest[endIndex+len(pemEnd)-1:])

	return
}

func decodeError(data, rest []byte) (*Block, []byte) {
	// 如果我们到了这里，那么我们已经拒绝了一个可能的期待，但
	// 最终无效的PEM块。我们需要从一个新的开始
	// 位置我们已经消耗了序言行，并且将消耗
	// 任何可以是标题行的行。然而，一个有效的序言
	// 行不是有效的标题行，因此我们无法使用
	// 任何后续块的前序行。因此，我们将永远
	// 查找任何有效块，无论其前面有多少字节。
	// None
	// 例如，如果输入是
	// None
	// -----开始格式错误的块-----
	// 看起来像标题行的垃圾
	// 或数据行，但没有结束行
	// None
	// -----开始实际块-----
	// 真实数据
	// -----结束实际块-----
	// None
	// 我们无法使用第一行开始进行分析
	// 现在将使用第二行开始重试。
	p, rest := Decode(rest)
	if p == nil {
		rest = data
	}
	return p, rest
}

const pemLineLength = 64

type lineBreaker struct {
	line [pemLineLength]byte
	used int
	out  io.Writer
}

var nl = []byte{'\n'}

func (l *lineBreaker) Write(b []byte) (n int, err error) {
	if l.used+len(b) < pemLineLength {
		copy(l.line[l.used:], b)
		l.used += len(b)
		return len(b), nil
	}

	n, err = l.out.Write(l.line[0:l.used])
	if err != nil {
		return
	}
	excess := pemLineLength - l.used
	l.used = 0

	n, err = l.out.Write(b[0:excess])
	if err != nil {
		return
	}

	n, err = l.out.Write(nl)
	if err != nil {
		return
	}

	return l.Write(b[excess:])
}

func (l *lineBreaker) Close() (err error) {
	if l.used > 0 {
		_, err = l.out.Write(l.line[0:l.used])
		if err != nil {
			return
		}
		_, err = l.out.Write(nl)
	}

	return
}

func writeHeader(out io.Writer, k, v string) error {
	_, err := out.Write([]byte(k + ": " + v + "\n"))
	return err
}

// Encode将b的PEM编码写入out。
func Encode(out io.Writer, b *Block) error {
	// 在写入任何输出之前检查无效块。
	for k := range b.Headers {
		if strings.Contains(k, ":") {
			return errors.New("pem: cannot encode a header key that contains a colon")
		}
	}

	// 下面的所有错误都是从基础io.Writer转发的，
	// 因此，现在写入数据是安全的。

	if _, err := out.Write(pemStart[1:]); err != nil {
		return err
	}
	if _, err := out.Write([]byte(b.Type + "-----\n")); err != nil {
		return err
	}

	if len(b.Headers) > 0 {
		const procType = "Proc-Type"
		h := make([]string, 0, len(b.Headers))
		hasProcType := false
		for k := range b.Headers {
			if k == procType {
				hasProcType = true
				continue
			}
			h = append(h, k)
		}
		// 必须首先写入Proc类型头。
		// 见RFC 1421第4.6.1.1节
		if hasProcType {
			if err := writeHeader(out, procType, b.Headers[procType]); err != nil {
				return err
			}
		}
		// 为确保输出的一致性，请编写按键排序的其他标题。
		sort.Strings(h)
		for _, k := range h {
			if err := writeHeader(out, k, b.Headers[k]); err != nil {
				return err
			}
		}
		if _, err := out.Write(nl); err != nil {
			return err
		}
	}

	var breaker lineBreaker
	breaker.out = out

	b64 := base64.NewEncoder(base64.StdEncoding, &breaker)
	if _, err := b64.Write(b.Bytes); err != nil {
		return err
	}
	b64.Close()
	breaker.Close()

	if _, err := out.Write(pemEnd[1:]); err != nil {
		return err
	}
	_, err := out.Write([]byte(b.Type + "-----\n"))
	return err
}

// EncodeTomery返回b的PEM编码。
// None
// 如果b具有无效的头并且无法编码，
// EncodeTomery返回零。如果重要的是
// 报告有关此错误情况的详细信息，请改用Encode。
func EncodeToMemory(b *Block) []byte {
	var buf bytes.Buffer
	if err := Encode(&buf, b); err != nil {
		return nil
	}
	return buf.Bytes()
}
